package pl;

import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
import javacard.framework.Util;
import javacard.security.RandomData;
import javacardx.apdu.ExtendedLength;

public class AppletControler extends Applet implements ExtendedLength {

	/* 0.1 */
	final static byte[] APPLET_VERSION = new byte[] { (byte) 0x30, 0x2E, 0x31 };

	/* serial number */
	private static byte[] serialNumber;

	final static byte[] SW_9000 = new byte[] { (byte) 0x90, 0x00 };
	final static short O = (short) 0x00;
	final static byte PIN_LENGTH = (short) 0x10;
	final static byte USER_SECRET_CODE_LEN = (byte) 0x08;
	final static byte RESETTING_CODE_LEN = (byte) 0x08;

	private final static byte GET_RESPONSE = (byte) 0xC0;
	private final static byte VERIFY = (byte) 0x20;
	private final static byte GET_DATA = (byte) 0xCA;
	private final static byte PUT_DATA = (byte) 0xDB;
	private final static byte PSO = (byte) 0x2A;
	private final static byte MSE = (byte) 0x22;
	private final static byte CHANGE_REFERENCE_DATA = (byte) 0x24;
	private final static byte RESET_RETRY_COUNTER = (byte) 0x2C;
	private final static byte GET_CHALLENGE = (byte) 0x84;
	private final static byte MUTUAL_AUTHENTICATE = (byte) 0x82;

	private final static byte GET_APPLET_INFO = (byte) 0xB2;
	private final static byte GET_CSN = (byte) 0xB8;

	private static final byte GENERATE_RANDOM = (byte) 0x85;

	private static final byte SEED =(byte) 0x86;

	private OwnerPIN userSecretCode;
	private OwnerPIN resettingCode;

	private RandomData rndGenerator;

	private MediatorEngine mediatorEngine;
	private SecureContext secureContext;

	private byte[] temporaryBuffer;
	private byte[] dataToSend;

	private boolean isMutualAuthenticationRequired;
	private boolean isUserPinAuthenticationRequired;
	private boolean isSecureMessagingRequired;
	private boolean isSetOutgoingUsed;
	private boolean useSm;
	private boolean isCase2or4;
	private boolean isCase1or3;
	private boolean isAuthenticated;
	private boolean usePinBeforeSign;

	private short incomingLength = 0;
	private short outgoingLength = 0;
	private short orignalLc = 0;
	private short lc;
	private short offsetChainMode;

	private short longBufferLen = 0;
	private short longBufferOffset = 0;

	private boolean isLongBuffer;
	private boolean copyBuffToTemp;

	private short cnt;
	private short cLen;

	private AppletControler() {

		secureContext = new SecureContext();
		isAuthenticated = false;

		/* user secret code */
		userSecretCode = new OwnerPIN((byte) 3, USER_SECRET_CODE_LEN);
		userSecretCode.update(new byte[] { 0x31, 0x31, 0x31, 0x31, (byte) 0xFF,
				(byte) 0xFF, (byte) 0xFF, (byte) 0xFF }, AppletControler.O,
				(byte) 8);

		/* resetting code */
		resettingCode = new OwnerPIN((byte) 3, RESETTING_CODE_LEN);
		resettingCode.update(new byte[] { 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
				(byte) 0xFF, (byte) 0xFF }, AppletControler.O, (byte) 8);

		/* Initialization of mediator engine */
		mediatorEngine = new MediatorEngine();

		rndGenerator = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);

		/* serial number array */
		serialNumber = new byte[8];
		rndGenerator.generateData(serialNumber, (short) 0, (short) 8);
	
		/* temporary buffers */
		temporaryBuffer = new byte[1536];
		dataToSend = new byte[1536];

		isMutualAuthenticationRequired = false;
		isUserPinAuthenticationRequired = true;
		isSecureMessagingRequired = false;
		usePinBeforeSign = false;
	}

	public boolean select() {

		secureContext.isEstablished = false;
		isAuthenticated = false;
		return true;
	}

	public void deselect() {

		userSecretCode.reset();
		resettingCode.reset();
		secureContext.isEstablished = false;
		isAuthenticated = false;
	}

	public static void install(byte bArray[], short bOffset, byte bLength)
			throws ISOException {
		AppletControler app = new AppletControler();
		app.register();
	}

	public void process(APDU arg0) throws ISOException {

		if (selectingApplet())
			return;

		byte[] buffer = arg0.getBuffer();

		isSetOutgoingUsed = false;
		isCase1or3 = false;
		isCase2or4 = false;
		useSm = false;

		/* check security conditions */

		if (buffer[ISO7816.OFFSET_INS] != GET_CHALLENGE
				&& buffer[ISO7816.OFFSET_INS] != MUTUAL_AUTHENTICATE
				&& buffer[ISO7816.OFFSET_INS] != GET_RESPONSE
				&& buffer[ISO7816.OFFSET_INS] != GET_CSN
				&& buffer[ISO7816.OFFSET_INS] != GENERATE_RANDOM
				&& buffer[ISO7816.OFFSET_INS] != GET_APPLET_INFO) {

			if (isMutualAuthenticationRequired) {

				if (!secureContext.isEstablished)
					ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);

				if (isSecureMessagingRequired) {

					useSm = true;

					if (!arg0.isSecureMessagingCLA())
						ISOException
								.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
					else {

						arg0.setIncomingAndReceive();

						orignalLc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);

						incomingLength = secureContext
								.verifyOrDecryptSecuredMessage(buffer,
										ISO7816.OFFSET_CDATA, orignalLc,
										dataToSend, (short) 0);

						// case 3 or 4
						if (incomingLength > 0) {

							Util.arrayCopy(dataToSend, (short) 0, buffer,
									ISO7816.OFFSET_CDATA, incomingLength);

							buffer[ISO7816.OFFSET_LC] = (byte) incomingLength;

						} else {
							// case 1 or 3
							incomingLength = 0;
						}

					}
				} else if (arg0.isSecureMessagingCLA())
					ISOException
							.throwIt(ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED);

			} else if (isUserPinAuthenticationRequired) {
				if (buffer[ISO7816.OFFSET_INS] != VERIFY
						&& buffer[ISO7816.OFFSET_INS] != RESET_RETRY_COUNTER) {

					if (!this.userSecretCode.isValidated())
						ISOException
								.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
				}
			} else
				ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
		}

		switch (buffer[ISO7816.OFFSET_INS]) {

		case CHANGE_REFERENCE_DATA:

			if (!isSecureMessagingRequired)
				incomingLength = arg0.setIncomingAndReceive();

			outgoingLength = changeReferenceData(buffer, (short) 0,
					incomingLength);

			break;

		case GET_RESPONSE:
			getResponse(arg0);
			return;

		case GET_DATA:

			if (!isSecureMessagingRequired)
				incomingLength = arg0.setIncomingAndReceive();

			outgoingLength = getData(buffer, dataToSend, (short) 0);

			break;

		case MSE:

			if (!isSecureMessagingRequired)
				incomingLength = arg0.setIncomingAndReceive();

			outgoingLength = setMse(buffer, (short) 0, incomingLength);

			break;

		case PUT_DATA:

			if (!isSecureMessagingRequired)
				incomingLength = arg0.setIncomingAndReceive();

			outgoingLength = putData(buffer, (short) 0, incomingLength,
					arg0.isCommandChainingCLA());

			break;

		case PSO:

			if (!isSecureMessagingRequired)
				incomingLength = arg0.setIncomingAndReceive();

			outgoingLength = pso(buffer, (short) 0, incomingLength, dataToSend,
					(short) 0);

			break;

		case VERIFY:

			if (!isSecureMessagingRequired)
				incomingLength = arg0.setIncomingAndReceive();

			if (!usePinBeforeSign) {
				if (!isUserPinAuthenticationRequired)
					ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
			}

			outgoingLength = verfify(buffer, (short) 0, incomingLength);

			break;

		case RESET_RETRY_COUNTER:

			if (!isSecureMessagingRequired)
				incomingLength = arg0.setIncomingAndReceive();

			outgoingLength = resetRetryCounter(buffer, (short) 0,
					incomingLength);

			break;

		case GET_CHALLENGE:

			isSetOutgoingUsed = true;

			outgoingLength = getChallenge(buffer, arg0.setOutgoing(),
					dataToSend, (short) 0);
			break;
		
		case GENERATE_RANDOM:

			isSetOutgoingUsed = true;

			outgoingLength = generateRandom(buffer, arg0.setOutgoing(),
					dataToSend, (short) 0);
			break;
			
		case SEED:

			isSetOutgoingUsed = true;

			//outgoingLength = putSeed(buffer, arg0.setOutgoing(),
			//		dataToSend, (short) 0);
			break;

		case GET_CSN:

			isSetOutgoingUsed = true;

			outgoingLength = getCardSerialNumber(buffer, arg0.setOutgoing(),
					dataToSend, (short) 0);

			break;

		case GET_APPLET_INFO:

			// if (!isSecureMessagingRequired)
			// incomingLength = arg0.setIncomingAndReceive();

			isSetOutgoingUsed = true;

			outgoingLength = getAppletInfo(buffer, ISO7816.OFFSET_CDATA,
					arg0.setOutgoing(), dataToSend, (short) 0);

			break;

		case MUTUAL_AUTHENTICATE:

			if (isUserPinAuthenticationRequired)
				ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);

			incomingLength = arg0.setIncomingAndReceive();

			outgoingLength = mutualAuthenticate(buffer, ISO7816.OFFSET_CDATA,
					incomingLength, dataToSend, (short) 0);
			break;

		default:
			ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
		}

		sendBuffer(arg0, dataToSend, (short) 0, outgoingLength);

	}

	private short putSeed(byte[] buffer, short bOffset, short bLen, byte[] dataToSend2,
			short offset) {
		
		if (buffer[ISO7816.OFFSET_P1] != 0x00 && buffer[ISO7816.OFFSET_P2] != 0x00)
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		else
		{
		
			rndGenerator.setSeed(buffer, bOffset, bLen);
		}
		return 0;
		//return setOutgoing;
		
	}

	private short generateRandom(byte[] buffer, short le,
			byte[] output, short outOff) {
		
		/*if (buffer[ISO7816.OFFSET_P1] != 0x00 && buffer[ISO7816.OFFSET_P2] != 0x00)
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		else
		{
			if (setOutgoing == 0)
				ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
			
			rndGenerator.generateData(dataToSend, offset, setOutgoing);
		}
		
		return setOutgoing;*/
		
		isAuthenticated = false;

		if (le == 0)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

		if (buffer[ISO7816.OFFSET_P1] == (byte) 0
				&& buffer[ISO7816.OFFSET_P2] == (byte) 0) {

			isCase2or4 = true;
			rndGenerator.generateData(dataToSend, (short)0, le);
			return le;

		} else {
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		}

		return 0;
	}

	private byte[] sw = new byte[2];

	private void sendBuffer(APDU arg0, byte[] buffer, short bOff, short buffLen) {

		if (!this.isSetOutgoingUsed)
			arg0.setOutgoing();

		if (isSecureMessagingRequired && secureContext.isEstablished && useSm) {

			short len = 0;

			if (isCase1or3) {

				Utils.shortOnByteArray(buffLen, sw, (short) 0);

				len = secureContext.buildResponseCase1and3(sw, (short) 0,
						(short) 2, buffer);

				arg0.setOutgoingLength(len);
				arg0.sendBytesLong(buffer, (short) 0, len);
			}

			else if (isCase2or4) {

				len = secureContext.buildResponseCase2and4(buffer, (short) 0,
						buffLen, temporaryBuffer, (short) 0);

				// ISOException.throwIt((short) ((short) 0x6000 + len));

				longBufferLen = len;

				if (len > 255) {

					short protocol = arg0.getProtocol();

					if (protocol == APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_A + 1) {

						if (!copyBuffToTemp) {

							longBufferOffset = 0;
							copyBuffToTemp = true;
						}

						arg0.setOutgoingLength((short) 0xFF);

						arg0.sendBytesLong(temporaryBuffer, bOff, (short) 0xFF);

						longBufferOffset += 0xFF;

						// cLen = (short) (len - 0xFF);

						cLen = (short) (longBufferLen - 0xFF);

						if (cLen > 254)
							cLen = 0xFF;

						ISOException.throwIt((short) ((short) 0x6100 + cLen));

					} else {

						arg0.setOutgoingLength(len);
						arg0.sendBytesLong(temporaryBuffer, bOff, len);
					}
				} else {

					copyBuffToTemp = false;
					arg0.setOutgoingLength(len);
					arg0.sendBytesLong(temporaryBuffer, bOff, len);
					longBufferOffset = 0;
				}

			} else
				ISOException.throwIt(ISO7816.SW_WRONG_DATA);

		} else {

			if (isCase1or3) {

				if (buffLen == 0)
					ISOException.throwIt(ISO7816.SW_NO_ERROR);
				else
					ISOException.throwIt(buffLen);
			}

			else if (isCase2or4) {
	
				if (buffLen > 254) {

					short protocol = arg0.getProtocol();

					// ISOException.throwIt((short) ((short) 0x6000 +
					// protocol));

					if (protocol == APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_A + 1
							|| protocol == APDU.PROTOCOL_T1) {

						//if (buffLen > 254 && buffLen < 500)
							
						
						// isLongBuffer = true;
						if (!copyBuffToTemp) {

							//ISOException.throwIt((short) ((short) 0x6300 +
							//		 1));
							
							//longBufferOffset = 0;

							
							Util.arrayCopy(buffer, (short) 0, temporaryBuffer,
									(short) 0, buffLen);
							
							longBufferLen = buffLen;
							
							longBufferOffset = 0;
							copyBuffToTemp = true;
						}
						
						arg0.setOutgoingLength((short) 0xFF);

						arg0.sendBytesLong(temporaryBuffer, bOff, (short) 0xFF);

						longBufferOffset += 0xFF;

						cLen = (short) (longBufferLen - 0xFF);

						if (cLen > 254)
							cLen = 0xFF;

						ISOException.throwIt((short) ((short) 0x6100 + cLen));

						/*
						 * cLen = (short) (longBufferLen - 0xFF); if (cLen >
						 * 254) cLen = 0xFF;
						 */

						// ISOException.throwIt((short) ((short) 0x6100 +
						// cLen));

						/*
						 * if (protocol == APDU.PROTOCOL_T1) {
						 * ISOException.throwIt((short) ((short) 0x6300 +
						 * cLen)); } else
						 */

					} else {

						copyBuffToTemp = false;

						Util.arrayCopy(buffer, (short) 0, temporaryBuffer,
								(short) 0, buffLen);

						if (protocol == APDU.PROTOCOL_T1) {
							arg0.setOutgoingLength(buffLen);
							arg0.sendBytesLong(temporaryBuffer, bOff, buffLen);
							ISOException
									.throwIt((short) ((short) 0x6100 + 0x66));

						} else {
							arg0.setOutgoingLength(buffLen);
							arg0.sendBytesLong(temporaryBuffer, bOff, buffLen);

						}

						longBufferOffset = 0;
					}
				}

				else {

					copyBuffToTemp = false;

					arg0.setOutgoingLength(buffLen);
					arg0.sendBytesLong(buffer, bOff, buffLen);

					longBufferOffset = 0;
				}

			} else
				ISOException.throwIt(ISO7816.SW_WRONG_DATA);

		}

		/*
		 * arg0.setOutgoingLength(buffLen); arg0.sendBytesLong(buffer, offset,
		 * buffLen); return;
		 */
	}

	private void getResponse(APDU apdu) {

		short le = apdu.setOutgoing();
		short protocol = apdu.getProtocol();

		if (protocol == APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_A + 1
				|| protocol == APDU.PROTOCOL_T1) {

			if (le == 0)
				le = 255;

			if (le > cLen)
				le = cLen;

			isSetOutgoingUsed = true;
			isCase2or4 = true;
			useSm = false;

			if (isSecureMessagingRequired) {

				longBufferLen -= le;

				sendBuffer(apdu, temporaryBuffer, longBufferOffset, le);
			} else {
				longBufferLen -= le;
				sendBuffer(apdu, temporaryBuffer, longBufferOffset, le);
			}
		} else {

			// short le = apdu.setOutgoing();

			if (apdu.getOutBlockSize() != le)
				ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);

			apdu.setOutgoingLength(le);
			apdu.sendBytes((short) 0, le);
		}

	}

	private short getAppletInfo(byte[] buffer, short bOff, short dLen,
			byte[] outBuff, short outOff) {
		/*
		 * if (le != 8) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		 */
		if (buffer[ISO7816.OFFSET_P1] == (byte) 0
				&& buffer[ISO7816.OFFSET_P2] == (byte) 0) {

			isCase2or4 = true;

			short off = outOff;

			outBuff[off] = 0x30;
			off++;

			outBuff[off] = 0x0e + 0x06;
			off++;

			/* Authentication mode */
			outBuff[off] = (byte) 0x80;
			off++;

			outBuff[off] = (byte) 0x01;
			off++;

			if (this.isMutualAuthenticationRequired)
				outBuff[off] = (byte) 0x01;
			else
				outBuff[off] = (byte) 0x00;

			off++;

			/* Secure messaging */
			outBuff[off] = (byte) 0x81;
			off++;

			outBuff[off] = (byte) 0x01;
			off++;

			if (this.isSecureMessagingRequired)
				outBuff[off] = (byte) 0x01;
			else
				outBuff[off] = (byte) 0x00;

			off++;

			/* Secret code before sign */
			outBuff[off] = (byte) 0x82;
			off++;

			outBuff[off] = (byte) 0x01;
			off++;

			if (this.usePinBeforeSign)
				outBuff[off] = (byte) 0x01;
			else
				outBuff[off] = (byte) 0x00;

			off++;

			/* Applet version */
			outBuff[off] = (byte) 0x83;
			off++;

			outBuff[off] = (byte) 0x03;
			off++;

			Util.arrayCopy(APPLET_VERSION, (short) 0, outBuff, off,
					(short) APPLET_VERSION.length);

			off += 0x03;

			/* Key container info */
			outBuff[off] = (byte) 0x84;
			off++;

			outBuff[off] = (byte) 0x01;
			off++;

			if (mediatorEngine.currentKid < 0)
				outBuff[off] = (byte) 0x7F;
			else
				outBuff[off] = mediatorEngine.currentKid;
			// return 16;

			//return 19;
			off ++;
			
			/* first use ?*/
			outBuff[off] = (byte) 0x85;
			off++;

			outBuff[off] = (byte) 0x01;
			off++;
			
			if (mediatorEngine.currentKid > -1)
			{
				if (mediatorEngine.keys[mediatorEngine.currentKid].isContainerUsed)
					outBuff[off] = (byte) 0x01;
				else
					outBuff[off] = (byte) 0x00;
			}
			else
			{
				outBuff[off] = (byte) 0x00;
			}
			
			//off += 0x03;
			return 22;
			//return 22;

		} else {
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		}

		//
		return 0;
	}

	private short mutualAuthenticate(byte[] buffer, short bOff, short dLen,
			byte[] outBuff, short outOff) {

		lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
		// lc = 48h ok

		if (lc == 0) {
			ISOException.throwIt((short) (0x63C0 + secureContext
					.getAuthenicationTries()));
			return 0;
		} else {

			short responseLen = secureContext.mutualAuthenticate(buffer,
					ISO7816.OFFSET_CDATA, lc, outBuff, outOff);

			/*
			 * short responseLen =
			 * prepareResponseForMutualAuthentication(buffer,
			 * ISO7816.OFFSET_CDATA, lc, outBuff, outOff);
			 */
			isCase2or4 = true;

			return responseLen;
			// sendBuffer(arg0, responseLen, inputBuffer);
		}
	}

	private short getCardSerialNumber(byte[] buffer, short le,
			byte[] dataToSend, short off) {

		if (le != 8)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

		if (buffer[ISO7816.OFFSET_P1] == (byte) 0
				&& buffer[ISO7816.OFFSET_P2] == (byte) 0) {

			isCase2or4 = true;
			Util.arrayCopy(serialNumber, (short) 0, dataToSend, off, (short) 8);
			return le;

		} else {
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		}

		return 0;
	}

	private short getChallenge(byte[] buffer, short le, byte[] output,
			short outOff) {

		isAuthenticated = false;

		if (le != 8)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

		if (buffer[ISO7816.OFFSET_P1] == (byte) 0
				&& buffer[ISO7816.OFFSET_P2] == (byte) 0) {

			isCase2or4 = true;
			le = secureContext.getChallenge(output, outOff, le);
			return le;

		} else {
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		}

		return 0;
	}

	private short setMse(byte[] buffer, short bOff, short incomingLength2) {
		// TODO Auto-generated method stub

		try {
			isCase1or3 = true;

			if (buffer[ISO7816.OFFSET_P2] == (byte) 0xB6) {
				mediatorEngine.selectKeyContainer(buffer[ISO7816.OFFSET_CDATA]);
				return ISO7816.SW_NO_ERROR;
			}
			else if(buffer[ISO7816.OFFSET_P2] == (byte) 0xB7)
			{
				mediatorEngine.setMechanism(buffer[ISO7816.OFFSET_CDATA]);
				return ISO7816.SW_NO_ERROR;
				
			} 
			else if(buffer[ISO7816.OFFSET_P2] == (byte) 0xB8)
			{
				mediatorEngine.destroyKeyContainer(buffer[ISO7816.OFFSET_CDATA]);
				return ISO7816.SW_NO_ERROR;
				
			}
			else {

				if (buffer[ISO7816.OFFSET_CDATA] == 0x00)
					this.isSecureMessagingRequired = false;
				else if (buffer[ISO7816.OFFSET_CDATA] == 0x01)
					this.isSecureMessagingRequired = true;

				/* --------------------------------------- */

				short off = ISO7816.OFFSET_CDATA;

				if (checkAuthTemplate(buffer, off, incomingLength2)) {

					short firstOccOff = Utils.findFirstOccurenceOfTagByte(
							buffer, off, incomingLength2, (byte) 0x80);

					firstOccOff += 2;

					if (buffer[firstOccOff] == 0x00) {
						isMutualAuthenticationRequired = false;
						isUserPinAuthenticationRequired = true;
					}

					else if (buffer[firstOccOff] == 0x01) {
						isMutualAuthenticationRequired = true;
						isUserPinAuthenticationRequired = false;

					} else if (buffer[firstOccOff] == 0x02) {

						isMutualAuthenticationRequired = true;
						isUserPinAuthenticationRequired = true;
					}

					firstOccOff = Utils.findFirstOccurenceOfTagByte(buffer,
							off, incomingLength2, (byte) 0x81);

					if (firstOccOff == -1)
						ISOException.throwIt((short) (ISO7816.SW_DATA_INVALID));

					firstOccOff += 2;

					// set
					if (buffer[firstOccOff] == 0x00)
						this.isSecureMessagingRequired = false;
					else if (buffer[firstOccOff] == 0x01) {
						this.isSecureMessagingRequired = true;
					}

					/*--------------------------------------------------------*/

					firstOccOff = Utils.findFirstOccurenceOfTagByte(buffer,
							off, incomingLength2, (byte) 0x82);

					if (firstOccOff == -1)
						ISOException.throwIt((short) (ISO7816.SW_DATA_INVALID));

					firstOccOff += 2;

					// set
					if (buffer[firstOccOff] == 0x00)
						this.usePinBeforeSign = false;
					else if (buffer[firstOccOff] == 0x01) {
						this.usePinBeforeSign = true;
					}

					/*
					 * setting key contatiner
					 * 
					 * firstOccOff = Utils.findFirstOccurenceOfTagByte(buffer,
					 * off, incomingLength2, (byte) 0x83);
					 * 
					 * if (firstOccOff == -1) ISOException.throwIt((short)
					 * (ISO7816.SW_DATA_INVALID));
					 * 
					 * firstOccOff += 2;
					 * 
					 * mediatorEngine.setKid(buffer[firstOccOff]);
					 */

					deselect();

					return ISO7816.SW_NO_ERROR;

				} else
					ISOException.throwIt((short) (ISO7816.SW_DATA_INVALID));
			}
		} catch (ISOException e) {

			if (e.getReason() == ISO7816.SW_NO_ERROR) {
				return e.getReason();
			} else if (e.getReason() == 0x6988) {
				ISOException.throwIt(e.getReason());
			} else {
				// ISOException.throwIt(e.getReason());
				isCase1or3 = true;
				return e.getReason();
			}
		}

		return 0;
	}

	private boolean checkAuthTemplate(byte[] buffer, short bOff,
			short incomingLength2) {

		boolean status = false;

		// A4 L 80 L1 01 81 L
		if (buffer[bOff] == (byte) 0xA4) {

			// skip len
			bOff += 2;

			if (buffer[bOff] == (byte) 0x80) {

				// skip len
				bOff += 3;

				if (buffer[bOff] == (byte) 0x81) {

					// status = true;

					bOff += 3;

					if (buffer[bOff] == (byte) 0x82) {

						status = true;

					}

				} else
					status = false;
			} else
				status = false;
		}

		return status;
	}

	private short pso(byte[] buffer, short bOff, short len, byte[] output,
			short offset) {

		try {

			cnt = 0;

			/* hash data -> response only SW bytes */
			if (buffer[ISO7816.OFFSET_P1] == (byte) 0x90
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x80) {

				lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);

				// hash -> expected input data in TLV 00 2A.... LC Data || 00
				// if (lc > 3 && lc < 67) {
				if (lc != 67) {
					/*
					 * short res = mediatorEngine.hashInIcc(buffer,
					 * ISO7816.OFFSET_CDATA, lc, output, offset); if (res > 0)
					 * isCase2or4 = true; else isCase1or3 = true; return res;
					 */

					mediatorEngine.hashInIcc(buffer, ISO7816.OFFSET_CDATA, lc);
					isCase1or3 = true;
					ISOException.throwIt(ISO7816.SW_NO_ERROR);

				} else
					ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

			} else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x90
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0xA0) {

				lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);

				// hash -> expected input data in TLV 00 2A.... LC Data || 00
				if (lc > 3 && lc < 67) {

					isCase2or4 = true;
					mediatorEngine.hashInIccFinal(buffer, ISO7816.OFFSET_CDATA,
							lc);

					// isCase1or3 = true;
					// ISOException.throwIt(ISO7816.SW_NO_ERROR);

					short pLen = (short) 0x14;

					// copy signature result
					Util.arrayCopy(mediatorEngine.hash, (short) 0, output,
							offset, pLen);

					return pLen;

				} else
					ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

			}

			else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x9E
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x9A) {

				isCase2or4 = true;

				// sign

				if (this.usePinBeforeSign) {
					if (!this.userSecretCode.isValidated())
						ISOException
								.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
				}

				mediatorEngine.sign();

				short pLen = (short) mediatorEngine.resLen;

				// copy signature result
				Util.arrayCopy(mediatorEngine.result, (short) 0, output,
						offset, pLen);

				return pLen;

			} else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x80
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x86) {

				lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
				isCase1or3 = true;
				
				mediatorEngine.verify(buffer, ISO7816.OFFSET_CDATA, lc);

				/*
				 * isCase2or4 = true; Util.arrayCopy(mediatorEngine.result,
				 * (short)0, dataToSend, (short)0, mediatorEngine.presigLen);
				 * return mediatorEngine.presigLen;
				 */

				return 0;

			} else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x80
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x87) {

				// decrypt data with user key

				isCase2or4 = true;

				lc = mediatorEngine.decryptWithUserKey(buffer,
						ISO7816.OFFSET_CDATA, len);

				Util.arrayCopy(mediatorEngine.result, (short) 0, output,
						offset, lc);

				return lc;

			} 
			
			else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x9E
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x9C) {

				isCase2or4 = true;

				// encrypt without hash

				if (this.usePinBeforeSign) {
					if (!this.userSecretCode.isValidated())
						ISOException
								.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
				}

				//mediatorEngine.encryptMessage(buffer, ISO7816.OFFSET_CDATA, len);

				mediatorEngine.sign(buffer, ISO7816.OFFSET_CDATA, len);

				short lenn = (short) mediatorEngine.resLen;
				 ///short pLen
				// copy signature result
				Util.arrayCopy(mediatorEngine.result, (short) 0, output,
						offset, lenn);
				
				return lenn;

			} else
				ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);

		} catch (ISOException e) {

			if (e.getReason() == ISO7816.SW_NO_ERROR) {
				return e.getReason();
			} else if (e.getReason() == 0x6988) {
				ISOException.throwIt(e.getReason());
			} else {
				// ISOException.throwIt(e.getReason());
				isCase1or3 = true;
				return e.getReason();
			}
		}
		return 0;
	}

	private short putData(byte[] buffer, short bOff, short incLen,
			boolean isCommandChaining) {

		try {

			lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);

			isCase1or3 = true;

			if (isCommandChaining) {

				// buff = 255
				Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, temporaryBuffer,
						offsetChainMode, incLen);

				offsetChainMode += lc;

			} else {
				Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, temporaryBuffer,
						offsetChainMode, incLen);

				if (buffer[ISO7816.OFFSET_P1] == (byte) 0x00
						&& buffer[ISO7816.OFFSET_P2] == (byte) 0x00) {

					/* initialize keys */
					mediatorEngine.initializeKeys(temporaryBuffer, (short) 0,
							(short) (offsetChainMode + lc));

				} else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x00
						&& buffer[ISO7816.OFFSET_P2] == (byte) 0x01) {

					/* put user certificate */
					mediatorEngine.setUserCertificate(temporaryBuffer,
							(short) 0, (short) (offsetChainMode + lc));

				} else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x00
						&& buffer[ISO7816.OFFSET_P2] == (byte) 0x02) {

					/* put certificate id */
					mediatorEngine.setCertificateId(temporaryBuffer,
							(short) 0, (short) (offsetChainMode + lc));

				} 
				else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x00
						&& buffer[ISO7816.OFFSET_P2] == (byte) 0x03) {

					/* put certificate label */
					mediatorEngine.setCertificateLabel(temporaryBuffer,
							(short) 0, (short) (offsetChainMode + lc));

				} 
				
				else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x00
						&& buffer[ISO7816.OFFSET_P2] == (byte) 0xFE) {

					mediatorEngine.destroyContainer(temporaryBuffer,
							(short) 0, (short) (offsetChainMode + lc));

				} 
				else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x00
						&& buffer[ISO7816.OFFSET_P2] == (byte) 0xFF) {

					/* put secret codes */
					// updateSecretCode(inputBuffer, ISO7816.OFFSET_CDATA,
					// incLen);

					/* put secret codes */
					secureContext.putSecretKeys(temporaryBuffer, (short) 0,
							(short) (offsetChainMode + lc));

				}

				/* last portion */
				offsetChainMode = 0;

			}

			ISOException.throwIt((short) (ISO7816.SW_NO_ERROR));

		} catch (ISOException e) {

			return e.getReason();
		}
		return 0;
	}

	private short getData(byte[] buffer, byte[] outputBuffer, short outOffset) {

		try {

			isCase2or4 = true;
			cnt = 0;

			if (buffer[ISO7816.OFFSET_P1] == (byte) 0x5F
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x49) {

				/* get public key modulus */
				short modulusLen = mediatorEngine.getPublicKeyModulus(
						outputBuffer, outOffset);

				// ISOException.throwIt((short) ((short)0x6300 + modulusLen));

				if (modulusLen == 0) {
					ISOException.throwIt(ISO7816.SW_DATA_INVALID);
				} else
					return modulusLen;

			} else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x5F
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x4E) {

				/* get user certificate */
				/*
				 * short cerLen = (short) mediatorEngine.certLen;
				 * 
				 * if (cerLen == 0)
				 * ISOException.throwIt(ISO7816.SW_DATA_INVALID);
				 * 
				 * Util.arrayCopy(mediatorEngine.certificate, (short) 0,
				 * dataToSend, (short) 0, cerLen);
				 * 
				 * return cerLen;
				 */

				short cerLen = (short) mediatorEngine.getUserCertificate(
						dataToSend, (short) 0);

				if (cerLen == 0)
					ISOException.throwIt(ISO7816.SW_DATA_INVALID);

				/*
				 * Util.arrayCopy(mediatorEngine.certificate, (short) 0,
				 * dataToSend, (short) 0, cerLen);
				 */

				return cerLen;

			} else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x5F
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x4A) {

				/* get public exponent */
				/*
				 * short pubExpLen = (short) mediatorEngine.publicExpLen;
				 * 
				 * if (pubExpLen == 0)
				 * ISOException.throwIt(ISO7816.SW_DATA_INVALID);
				 * 
				 * Util.arrayCopy(mediatorEngine.publicKeyExponent, (short) 0,
				 * dataToSend, (short) 0, pubExpLen);
				 * 
				 * return pubExpLen;
				 */

				// ISOException.throwIt((short) (0x6300));

				/* modification */
				short pubExpLen = (short) mediatorEngine.getPublicKeyExponent(
						dataToSend, (short) 0);

				if (pubExpLen == 0)
					ISOException.throwIt(ISO7816.SW_DATA_INVALID);

				return pubExpLen;

			} else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x5F
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x4B) {

				/* key evo */
				short keySeed = (short) mediatorEngine.keyEvoEng.kLen;

				if (keySeed == 0)
					ISOException.throwIt(ISO7816.SW_DATA_INVALID);

				/*
				 * Util.arrayCopy(mediatorEngine.keyEvoEng.key, (short) 0,
				 * dataToSend, (short) 0, keySeed);
				 */

				mediatorEngine.getEvolutionKey(dataToSend, (short) 0);

				/*
				 * Util.arrayCopy(, (short) 0, dataToSend, (short) 0, keySeed);
				 */

				return keySeed;

			}
			else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x5F
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x4C) {

				/* certificate Id */
				short cerIdLen = (short) mediatorEngine.getUserCertificateId(
						dataToSend, (short) 0);

				if (cerIdLen == 0)
					ISOException.throwIt(ISO7816.SW_DATA_INVALID);

				return cerIdLen;

			}
			else if (buffer[ISO7816.OFFSET_P1] == (byte) 0x5F
					&& buffer[ISO7816.OFFSET_P2] == (byte) 0x4D) {

				/* certificate Id */
				short cerIdLen = (short) mediatorEngine.getCertificateLabel(
						dataToSend, (short) 0);

				if (cerIdLen == 0)
					ISOException.throwIt(ISO7816.SW_DATA_INVALID);

				return cerIdLen;

			}

			else
				ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
		} catch (ISOException e) {

			if (e.getReason() == ISO7816.SW_NO_ERROR) {
				return e.getReason();
			} else if (e.getReason() == 0x6988) {
				ISOException.throwIt(e.getReason());
			} else {
				// ISOException.throwIt(e.getReason());
				isCase1or3 = true;
				isCase2or4 = false;
				return e.getReason();
			}
		}

		return 0;
	}

	/* secret code management */

	private short verfify(byte[] buffer, short bOff, short bLen) {

		short kid = (short) buffer[ISO7816.OFFSET_P2];
		kid &= (byte) 0x0F;

		isCase1or3 = true;

		switch (kid) {

		/* user secret code */
		case 0x01:

			if (incomingLength > USER_SECRET_CODE_LEN)
				ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

			if (!userSecretCode.check(buffer, ISO7816.OFFSET_CDATA,
					(byte) incomingLength)) {

				this.isAuthenticated = false;
				checkUserCodeTries();
			} else
				this.isAuthenticated = true;

			break;

		/* resetting code */
		case 0x02:

			if (incomingLength > RESETTING_CODE_LEN)
				ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

			if (!resettingCode.check(buffer, ISO7816.OFFSET_CDATA,
					(byte) incomingLength)) {

				checkRcTries();
			}
			break;

		default:
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		}

		return 0;

	}

	private short changeReferenceData(byte[] buffer, short bOff, short bLen) {
		isCase1or3 = true;

		short kid = (short) buffer[ISO7816.OFFSET_P2];
		kid &= (byte) 0x0F;

		if (incomingLength != 0x10)
			ISOException.throwIt(ISO7816.SW_WRONG_DATA);

		switch (kid) {

		// user secret code
		case 0x01:

			if (userSecretCode.check(buffer, ISO7816.OFFSET_CDATA,
					USER_SECRET_CODE_LEN)) {

				userSecretCode.update(buffer,
						(short) (ISO7816.OFFSET_CDATA + USER_SECRET_CODE_LEN),
						USER_SECRET_CODE_LEN);
			} else
				checkUserCodeTries();

			break;

		// resetting code
		case 0x02:

			if (resettingCode.check(buffer, ISO7816.OFFSET_CDATA,
					RESETTING_CODE_LEN)) {

				resettingCode.update(buffer,
						(short) (ISO7816.OFFSET_CDATA + RESETTING_CODE_LEN),
						RESETTING_CODE_LEN);
			} else
				checkRcTries();

			break;

		default:
			ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
		}
		return 0;
	}

	private short resetRetryCounter(byte[] buffer, short bOff, short bLen) {
		// ins = 0x2C
		isCase1or3 = true;
		short kid = (short) buffer[ISO7816.OFFSET_P2];
		kid &= (byte) 0x0F;

		short mode = buffer[ISO7816.OFFSET_P1];

		switch (mode) {

		case 0x00:

			if (kid == (short) 1) {

				if (incomingLength != 0x10)
					ISOException.throwIt(ISO7816.SW_WRONG_DATA);

				if (resettingCode.check(buffer, ISO7816.OFFSET_CDATA,
						(byte) 0x08)) {

					// rc code verified successfully, change referenced user sc

					JCSystem.beginTransaction();

					userSecretCode.update(buffer,
							(short) (ISO7816.OFFSET_CDATA + 0x08), (byte) 8);
					userSecretCode.resetAndUnblock();

					JCSystem.commitTransaction();

				} else
					checkRcTries();
			}

			break;

		case 0x01:

			if (incomingLength != 0x08)
				ISOException.throwIt(ISO7816.SW_WRONG_DATA);

			if (resettingCode.check(buffer, ISO7816.OFFSET_CDATA,
					(byte) incomingLength)) {

				userSecretCode.resetAndUnblock();
			} else
				checkRcTries();

			break;

		default:
			break;

		}

		return 0;
	}

	private void checkRcTries() {
		short tries = resettingCode.getTriesRemaining();

		if (tries > 0) {
			tries += (short) 0x63C0;
			ISOException.throwIt(tries);
		} else
			ISOException.throwIt((short) 0x6983);
	}

	private void checkUserCodeTries() {
		short tries = userSecretCode.getTriesRemaining();

		if (tries > 0) {
			tries += (short) 0x63C0;
			ISOException.throwIt(tries);
		} else
			ISOException.throwIt((short) 0x6983);
	}

	private void updateSecretCode(byte[] inputBuffer, byte offsetCdata, short lc) {

		// FF 20 (tag) 20 (length) 83 01 xx (Pin reference) 84 0x xx yy zz (Pin
		// value)
		// parse input data in TLV format -> Tag FF 20
		// TODO Auto-generated method stub
	}
}
